package server

import (
	"errors"
	"net/http"
	"os"
	"sieve_admin_server_userpass/pkg/config"
	"sieve_admin_server_userpass/pkg/crypto/aes"
	"sieve_admin_server_userpass/pkg/json"
	"sieve_admin_server_userpass/pkg/log"
	"sieve_admin_server_userpass/pkg/util/rand"
	"strconv"
	"strings"
)

var (
	fSessExpire = config.Int("session.expire", 3600, "session过期时间 秒")
	fCookieName = config.String("session.cookie", "session", "session cookie name")

	tpls = map[string]*tpl{
		"login": {Default: tplLogin, File: config.String("web.tpl.login", "", "")},
		"error": {Default: tplErr, File: config.String("web.tpl.error", "", "")},
	}
)

type loginToken struct {
	Uid int
}

func getLoginToken(r *http.Request) *loginToken {
	token := ""
	if ck, err := r.Cookie("app_" + *fCookieName); err == nil {
		token = ck.Value
	}
	if *fDebugMode {
		if token == "" {
			token = r.Form.Get("_t")
		}
	}
	if token == "" {
		return nil
	}
	ss := strings.SplitN(token, "-", 2)
	uid, err := strconv.Atoi(ss[0])
	if err != nil {
		return nil
	}
	if *fDebugMode && *fDebugSession != "" && ss[1] == *fDebugSession {
		return &loginToken{Uid: uid}
	}
	skey := getSessionKey(uid)
	if skey == "" {
		return nil
	}
	b, err := aes.DecryptString([]byte(skey), ss[1])
	if err != nil {
		return nil
	}
	tk := &loginToken{}
	if err := json.Unmarshal(b, &tk); err != nil {
		return nil
	}
	if uid != tk.Uid {
		return nil
	}
	return tk
}

func GetLoginUid(r *http.Request) int {
	tk := getLoginToken(r)
	if tk == nil {
		return 0
	}
	return tk.Uid
}
func generateToken(uid int) (string, error) {
	tk := &loginToken{Uid: uid}
	b, err := json.Marshal(tk)
	if err != nil {
		return "", err
	}
	skey := rand.RandStr(32)
	if err := setSessionKey(uid, skey); err != nil {
		return "", err
	}
	str, err := aes.EncryptToString([]byte(skey), b)
	return str, err
}
func GenerateToken(uid int) string {
	if *fDebugMode {
		str, _ := generateToken(uid)
		return str
	}
	return ""
}
func createLoginToken(uid int, w http.ResponseWriter) error {
	str, err := generateToken(uid)
	if err != nil {
		return err
	}
	http.SetCookie(w, &http.Cookie{
		Name:     "app_" + *fCookieName,
		Value:    strconv.Itoa(uid) + "-" + str,
		HttpOnly: true,
		Path:     *fPathRoot,
		Secure:   *fTlsCert != "" && *fTlsKey != "",
	})
	return nil
}

func handleLogin(w http.ResponseWriter, r *http.Request) {
	if r.Method != http.MethodPost {
		showLoginPage(w)
		return
	}
	if err := func() error {
		r.ParseForm()
		args := r.PostForm
		usr := args.Get("username")
		if !checkUsername(usr) {
			return errors.New("用户名格式错误")
		}
		pwd := args.Get("password")
		uid, err := getLoginUid(usr, pwd)
		if err != nil {
			return err
		}
		if !isAdmin(uid) {
			// u := GetUser(uid)
			// if u == nil {
			//	return errors.New("没有权限")
			// }
			// if perms, ok := rolePerm[u.Title]; !ok || len(perms) == 0 {
			//	return errors.New("没有权限")
			// }
		}
		// IP, _, _ := net.SplitHostPort(r.RemoteAddr)
		// db.InsertOperation(uid, "loginIn", "login", r.Header.Get("User-Agent"), IP)
		if err := createLoginToken(uid, w); err != nil {
			log.Err("createLoginToken", err)
			return errors.New("内部错误")
		}
		http.Redirect(w, r, *fPathRoot, http.StatusFound)
		return nil
	}(); err != nil {
		log.Err("handleLogin", err)
		showErrPage(w, err)
	}
}

const unameAllow = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-_."

func checkUsername(s string) bool {
	if len(s) < 1 || len(s) > 64 {
		return false
	}
	for i := 0; i < len(s); i++ {
		if !strings.Contains(unameAllow, string(s[i])) {
			return false
		}
	}
	return true
}

func handleLogout(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Access-Control-Allow-Origin", "*")
	http.SetCookie(w, &http.Cookie{
		Name:   "app_" + *fCookieName,
		MaxAge: -1,
		Path:   *fPathRoot,
	})
	http.Redirect(w, r, *fPathRoot+"api/login", http.StatusFound)
}

func showLoginPage(w http.ResponseWriter) {
	tpl := getTplFile("login")
	if tpl == "" {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	w.Write([]byte(strings.ReplaceAll(tpl, "{{Title}}", *ftitle)))
}

func showErrPage(w http.ResponseWriter, err error) {
	tpl := getTplFile("error")
	if tpl == "" {
		w.WriteHeader(http.StatusInternalServerError)
		return
	}
	rep := strings.NewReplacer("{{Title}}", *ftitle, "{{ErrMsg}}", err.Error())
	w.WriteHeader(http.StatusForbidden)
	w.Write([]byte(rep.Replace(tpl)))
}

func getTplFile(name string) string {
	t, ok := tpls[name]
	if !ok {
		return ""
	}
	if f := *t.File; f != "" {
		if b, _ := os.ReadFile(f); b != nil {
			return string(b)
		}
	}
	return t.Default
}

type tpl struct {
	Default string
	File    *string
}

var tplLogin = `<html>
<head>
<meta charset="UTF-8">
<title>登录</title>
<style>
*{ margin: 0; padding: 0; font-family: "Microsoft YaHei"; font-weight: normal; font-style: normal; }
body{ background: #f6f6f6; text-align: center; }
.head{ width: 100%; height: 90px; line-height: 90px; background: #ffffff; }
.head-content{ width: 300px; margin: 0 auto; text-align: left; }
.title{ line-height: 90px; font-size: 26px; color:#00c3b3; vertical-align: middle; }
.form-input{ display: block; width: 300px; height: 50px; border-radius: 4px; font-size: 20px; border: 1px solid #d6d7dc; text-indent: 10%; color: #b3b3b3; outline: none; margin: 20px auto; }
.form-input::-moz-placeholder,.form-input::-webkit-input-placeholder{ color: #b3b3b3; }
.form-login{ display: block; border:none; margin: 18px auto; background: #00c3b3; color:#feffff; width: 300px; height: 40px; border-radius: 4px; font-size: 18px; }
</style>
</head>  
<body>
<header class="head">
  <p class="head-content">
  <span class="title">{{Title}}</span>
  </p>
</header>
<article>
  <form method="post">
    <input name="username" type="text" placeholder="账号" class="form-input" required />
    <input name="password" type="password" placeholder="密码" class="form-input" required />
    <input type="submit" value="登录" class="form-login" />
  </form>
</article>
</body>
</html>`

var tplErr = `<html>
<head>
<meta charset="UTF-8">
<title>出错了</title>
<style>
*{ margin: 0; padding: 0; font-family: "Microsoft YaHei"; font-weight: normal; font-style: normal; }
body{ background: #f6f6f6; text-align: center; }
.head{ width: 100%; height: 90px; line-height: 90px; background: #ffffff; }
.head-content{ width: 300px; margin: 0 auto; text-align: left; }
.title{ line-height: 90px; font-size: 26px; color:#00c3b3; vertical-align: middle; }
.err{ color: #ff4c41; text-align: center; margin: 20px 0 0 0; font-style: normal; font-weight: 400; font-size: 22px;}
.btn{ display: block; border:none; margin: 18px auto; background: #00c3b3; color:#feffff; width: 100px; height: 40px; border-radius: 4px; font-size: 18px; }
</style>
</head>  
<body>
<header class="head">
  <p class="head-content">
  <span class="title">{{Title}}</span>
  </p>
</header>
<article>
  <h3 class="err">{{ErrMsg}}</h3>
  <button class="btn" onclick="javascript:window.history.back()">后退</button>
</article>
</body>
</html>`
